package com.loadburn.heron.route;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Stage;
import com.loadburn.heron.bind.DefaultResponse;
import com.loadburn.heron.bind.Request;
import com.loadburn.heron.bind.RequestBinder;
import com.loadburn.heron.cache.WebCache;
import com.loadburn.heron.complier.Compilers;
import com.loadburn.heron.converter.ValidationConverter;
import com.loadburn.heron.exceptions.HeronException;
import com.loadburn.heron.render.AdditionAttributes;
import com.loadburn.heron.render.RedirectAttributes;
import com.loadburn.heron.render.Result;
import net.jcip.annotations.Immutable;

import javax.inject.Singleton;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.io.IOException;
import java.util.List;
import java.util.Set;

@Immutable
@Singleton
class RoutingDispatcherDefault implements RoutingDispatcher {

    private final HeronPage heronPage;
    private final RequestBinder binder;
    private final Provider<WebCache> webCacheProvider;
    private final Compilers compilers;

    @Inject
    private ValidationConverter validationConverter;

    @Inject
    private Stage currentStage;

    @Inject
    private Provider<AdditionAttributes> additionAttributes;
    @Inject
    private Provider<RedirectAttributes> redirectAttributes;

    @Inject
    public RoutingDispatcherDefault(HeronPage heronPage, RequestBinder<String> binder, Provider<WebCache> webCacheProvider,
                                    Compilers compilers) {
        this.heronPage = heronPage;
        this.binder = binder;
        this.webCacheProvider = webCacheProvider;
        this.compilers = compilers;
    }

    public <P> Object dispatch(Request<P> request) throws IOException {
        String uri = request.path();

        HeronPage.Page page = webCacheProvider.get().get(uri);
        webCacheProvider.get().remove(uri);

        if (null == page) {
            page = heronPage.get(uri);
        }
        if (null == page) {
            return null;
        }

        if (Stage.DEVELOPMENT == currentStage) {
            compilers.compilePage(page);
        }

        final Object instance = page.instantiate();

        bindPage(binder, request, instance);

        return executePathMethod(validationConverter, request, page, instance,
                additionAttributes.get(), redirectAttributes.get());

    }

    @SuppressWarnings("unchecked")
    private static <P> void bindPage(final RequestBinder binder, final Request<P> request, final Object instance) {
        binder.bind(request, instance);
    }

    @SuppressWarnings("unchecked")
    private static <P> Object executePathMethod(final ValidationConverter validationConverter,
                                                final Request<P> request, final HeronPage.Page page, final Object instance,
                                                final AdditionAttributes additionAttributes,
                                                final RedirectAttributes redirectAttributes)
            throws IOException {

        List<String> errors = null;
        Object redirect = null;
        try {

            final String method = request.method();
            final String pathInfo = request.path();

            redirect = page.doMethod(method.toLowerCase(), instance, pathInfo, request);

        } catch (ValidationException ve) {
            ve.printStackTrace();
            ConstraintViolationException cve = (ConstraintViolationException) ve.getCause();
            Set<? extends ConstraintViolation<?>> scv = cve.getConstraintViolations();
            errors = validationConverter.to(scv);
        }

        DefaultResponse response = new DefaultResponse(instance);
        response.setErrors(errors);
        response.setAdditionAttributes(additionAttributes);
        response.setRedirectAttributes(redirectAttributes);
        if (redirect != null) {
            if (redirect instanceof String) {

                response.redirect((String) redirect);
            } else if (redirect instanceof Result<?>) {
                return redirect;
            } else {
                throw new HeronException("方法返回类型错误,返回类型可以回String或Result.");
            }
        } else {

            page.renderInstance().render(instance, response);
        }

        return response;

    }

}